Skip to content

fix(feishu): support bot-to-bot group mentions - #29662

Open
ohmyskyhigh wants to merge 1 commit into
NousResearch:mainfrom
ohmyskyhigh:fix/feishu-agent-to-agent-group-chat
Open

ohmyskyhigh wants to merge 1 commit into
NousResearch:mainfrom
ohmyskyhigh:fix/feishu-agent-to-agent-group-chat

Conversation

@ohmyskyhigh

@ohmyskyhigh ohmyskyhigh commented May 21, 2026 •

Copy link
Copy Markdown

Summary

Adds the missing Feishu/Lark gateway pieces needed for trusted Hermes bot-to-bot collaboration in group chats:

  • Supports native outbound @mentions from Hermes Feishu messages via FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES, converting configured @Name aliases into Feishu post at entities.
  • Keeps native outbound mentions working even when the response contains Markdown-table-shaped text, instead of downgrading to plain text and losing the mention entity.
  • Loads the profile-local .env before send_message constructs a Feishu adapter, so cron/tool-driven Feishu sends see the same mention-alias configuration as the gateway process.
  • Adds FEISHU_DM_POLICY=disabled for agents that should participate only in groups and ignore direct Feishu DMs.
  • Improves bot-event admission diagnostics by logging bot drop reasons at info level.
  • Normalizes Feishu mention IDs when the platform provides a bare ou_... or u_... string without id_type, so strict group mention admission can still recognize the receiving bot.

Problem

Hermes Feishu bots can be placed together in shared collaboration groups, but the existing behavior made agent-to-agent handoff unreliable:

  1. Outbound text such as @MOSS please inspect was sent as literal text, not a native Feishu mention, so the receiving bot was not necessarily notified or admitted by mention-gated group logic.
  2. The fallback to plain text for Markdown-table-shaped content could silently strip the rich-text mention entity that Feishu requires for a real @mention.
  3. send_message calls from non-gateway processes did not necessarily load profile-local Feishu mention aliases before building the adapter.
  4. Some Feishu payloads represent mentions as bare ID strings; those were treated as missing IDs and could cause FEISHU_REQUIRE_MENTION=true / bot-to-bot mention gates to drop otherwise valid messages.
  5. Group-only agents needed a simple way to reject Feishu DMs without disabling their group participation.

Solution

  • Parse configured outbound mention aliases from env and split outbound message rows into Feishu rich-text elements, emitting {"tag": "at", "user_id": ..., "user_name": ...} for matching aliases.
  • Avoid alias replacement inside inline-code spans and require sensible mention boundaries so emails/fragments are not converted accidentally.
  • Prefer rich-text post payloads when an outbound native mention is present; use plain text elements inside that post where needed so table-shaped content does not force a downgrade.
  • Load get_hermes_home() / ".env" in the Feishu send_message path before constructing the adapter.
  • Add FEISHU_DM_POLICY parsing and admission handling (open by default, disabled to reject p2p messages).
  • Normalize mention IDs through _extract_mention_ids() and reuse that logic in _message_mentions_bot() so bare ou_... / u_... mention strings are recognized.

Related issues

Refs #21366 — the bare-ID normalization addresses one concrete Feishu mention-shape case where the gateway mention gate can misclassify a valid bot mention.

Refs #25728 — this does not implement message buffering, but it improves the underlying Feishu group-mention reliability needed for mention-triggered group workflows.

Refs #29245 — this does not fully solve Markdown table rendering, but it prevents table-shaped content from forcing a plain-text downgrade when a native Feishu @mention must be preserved.

Refs #17847 — related multi-bot Feishu safety context; this PR keeps bot sender admission opt-in and improves diagnostics rather than broadly processing all bot-originated events.

Tests

Focused tests run locally:

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='

Result:

71 passed, 2 warnings in 1.42s

The covered cases include:

  • outbound alias conversion into Feishu at rich-text entities;
  • native mentions preserved with Markdown-table-shaped content;
  • alias matching does not trigger on email/fragments;
  • aliases inside inline code remain plain text;
  • FEISHU_DM_POLICY parsing, defaulting, and invalid-value warning behavior;
  • p2p rejection when DM policy is disabled while group admission still works;
  • bot mention admission for bare ou_... and u_... mention IDs;
  • bot-to-bot group admission with allow_bots=mentions and strict mention gating.

@daimon-nous daimon-nous Bot added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter P3 Low — cosmetic, nice to have labels May 21, 2026
@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from dce4c9a to 2127c4f Compare May 21, 2026 08:28

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused Feishu reliability work. The underlying outbound-mention and bare-ID issues are still present on current main, but this needs a careful port.

Problems

  • The alias regex at gateway/platforms/feishu.py:647 only checks the character after an alias. user@MOSS.com therefore matches @MOSS because . is allowed, despite the stated email-safety goal.
  • The new p2p return at gateway/platforms/feishu.py:4110 must preserve main's current p2p pairing/allowlist checks at plugins/platforms/feishu/adapter.py:4256-4268 when salvaged.
  • The live adapter moved to plugins/platforms/feishu/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef.

Suggested changes

  • Port the implementation to the bundled plugin, add a left-boundary email regression, and layer DM disabling ahead of the existing p2p authorization path.

Automated hermes-sweeper review.

Comment thread gateway/platforms/feishu.py Outdated
if not names:
return [{"tag": text_tag, "text": text}]
pattern = re.compile(
r"@(" + "|".join(re.escape(name) for name in names) + r")(?=$|[\s\t\n\r.,;:!?、,。;:!?()\[\]{}<>\"'`])"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only checks the trailing boundary. With alias MOSS, user@MOSS.com matches because . is allowed here, so an email address is converted into an at entity despite the email-safety claim. Add a preceding boundary check and a regression test.

@@ -3965,6 +4108,8 @@ def _admit(self, sender: Any, message: Any) -> Optional[RejectReason]:
return "bot_not_mentioned"

if not is_group:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When salvaging this onto current main, keep the existing p2p pairing/allowlist checks after this policy rejection. Current main performs those checks in plugins/platforms/feishu/adapter.py:4256-4268; returning unconditionally here after the disabled check would bypass them.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from 2127c4f to b99c6c1 Compare July 14, 2026 15:43
@ohmyskyhigh

Copy link
Copy Markdown
Author

Ported and force-pushed this PR onto current origin/main.

Addressed the sweeper/admin feedback:

  • moved the implementation from the old gateway/platforms/feishu.py path to plugins/platforms/feishu/adapter.py;
  • added a left-boundary email regression (user@MOSS.com) so outbound alias matching does not create a native at inside email/fragments;
  • layered FEISHU_DM_POLICY=disabled ahead of the existing p2p allow-all / pairing / allowlist checks, preserving the current p2p authorization path when DM policy is open;
  • kept FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES compatibility with both Name=ou_xxx and Name:ou_xxx entries;
  • normalized bare ou_... / u_... mention IDs and made _message_mentions_bot() reuse the same ID extractor.

Validation on the rebased branch:

python -m compileall -q plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# passed

git diff --check
# passed

ruff check plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# All checks passed!

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='
# 82 passed, 5 warnings in 1.38s

python -m pytest tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/test_feishu_bot_auth_bypass.py -q -o 'addopts='
# 298 passed, 5 warnings in 3.44s

python -m pytest tests/tools/test_send_message_tool.py -q -o 'addopts='
# 152 passed, 1 warning in 1.91s

The PR is now mergeable from GitHub's perspective.

@ohmyskyhigh

Copy link
Copy Markdown
Author

Adding a code map for maintainers/admins so the test files in this PR are easier to orient around.

Why the tests/gateway/* changes are included

They are regression coverage for the exact Feishu behavior changed here, not unrelated test-only churn:

  • tests/gateway/test_feishu.py

    • Covers the outbound path: agent text like @MOSS ... → Feishu post payload with a native at tag.
    • Includes the review-requested left-boundary regression (user@MOSS.com) so alias matching does not create false mentions inside emails/fragments.
    • Also pins table-shaped content, inline-code exclusion, longest-alias precedence, and fail-soft parsing of bad alias entries.
  • tests/gateway/test_feishu_bot_admission.py

    • Covers the inbound/admission path for bot-to-bot group messages.
    • Pins the bare mention ID normalization case where Feishu gives mention.id = "ou_..." or "u_..." with no id_type; those now resolve through _extract_mention_ids() and are recognized by _message_mentions_bot().
    • Pins FEISHU_DM_POLICY=disabled as p2p-only, ahead of the existing p2p allow-all / pairing / allowlist logic, while group admission remains available.
  • tests/gateway/feishu_helpers.py

    • Existing shared test fixture file; this PR only extends the adapter skeleton with _dm_policy so the p2p/group admission regression can exercise the same attribute the real adapter sets via FeishuAdapterSettings.

Runtime flow to review

Outbound native mention path:

agent response text
  -> FeishuAdapter._build_outbound_payload()
  -> _content_has_outbound_mention_alias()
  -> _build_outbound_mention_text_post_payload()
  -> Feishu post content containing {"tag": "at", "user_id": ..., "user_name": ...}

Inbound bot-to-bot group admission path:

Feishu message event
  -> _admit(sender, message)
  -> bot sender policy: FEISHU_ALLOW_BOTS={none|mentions|all}
  -> group mention gate when FEISHU_REQUIRE_MENTION=true
  -> _message_mentions_bot()
  -> _extract_mention_ids() handles typed IDs and bare ou_/u_ strings

DM policy path:

p2p message
  -> FEISHU_DM_POLICY=disabled rejects p2p early
  -> otherwise existing p2p allow-all / pairing-mode / allowlist behavior is preserved

group message
  -> unaffected by DM policy; continues through group policy + mention admission

This should make the PR easier to review file-by-file: adapter.py contains the behavior, while the three test files pin the outbound payload construction, inbound mention normalization/admission, and the small shared fixture needed for that admission matrix.

@ohmyskyhigh

Copy link
Copy Markdown
Author

For maintainers reviewing the code changes, here is the shortest file-by-file map of the implementation.

plugins/platforms/feishu/adapter.py

This is the only runtime file changed in the PR. The changes are intentionally split into three independent paths:

  1. Outbound native @mention construction

    • New helpers parse FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES.
    • _build_outbound_payload() now checks whether the outgoing text contains a configured alias.
    • If it does, the adapter sends a Feishu post payload with real at elements instead of literal text.
    • Boundary checks prevent false positives such as user@MOSS.com, and inline-code spans are left as plain text.

    Review path:

    _build_outbound_payload()
      -> _content_has_outbound_mention_alias()
      -> _build_outbound_mention_text_post_payload()
      -> _split_text_with_outbound_mentions()
    
  2. Inbound bot-to-bot mention admission

    • _extract_mention_ids() now accepts Feishu's bare mention string forms:
      • ou_... as open_id
      • u_... as user_id
    • _message_mentions_bot() reuses _extract_mention_ids() so the same normalization is used by the group mention gate.

    Review path:

    _admit()
      -> _message_mentions_bot()
      -> _extract_mention_ids()
    
  3. DM disabling without changing group behavior

    • FEISHU_DM_POLICY is parsed into FeishuAdapterSettings.
    • FEISHU_DM_POLICY=disabled rejects only p2p messages.
    • Existing p2p allow-all / pairing / allowlist logic is preserved when DM policy is open.
    • Group messages continue through the existing group policy + mention gate.

    Review path:

    FeishuAdapter._load_settings()
      -> FeishuAdapter._apply_settings()
      -> _admit() p2p branch
    
  4. Standalone send_message parity

    • _standalone_send() loads the profile-local .env before constructing the transient Feishu adapter.
    • This keeps cron/tool sends consistent with the live gateway for outbound mention aliases.

Tests

The tests are included to make each changed runtime path reviewable and to prevent regressions:

  • tests/gateway/test_feishu.py

    • outbound alias conversion to Feishu native at tags;
    • table-shaped content preserving native mentions;
    • inline-code exclusion;
    • false-positive protection for email/fragments, including the review-requested user@MOSS.com case;
    • longest-alias matching and bad-entry fail-soft behavior.
  • tests/gateway/test_feishu_bot_admission.py

    • settings parsing for FEISHU_DM_POLICY;
    • bare ou_... / u_... mention ID normalization;
    • _message_mentions_bot() recognition of those normalized IDs;
    • strict bot-to-bot group admission with FEISHU_ALLOW_BOTS=mentions and FEISHU_REQUIRE_MENTION=true;
    • p2p-only DM rejection while group admission still works.
  • tests/gateway/feishu_helpers.py

    • shared fixture support for the admission matrix; the PR only extends the existing skeleton with the same _dm_policy attribute the real adapter sets.

Practical review order

A quick way to review the PR is:

  1. Read _build_outbound_payload() and the outbound alias helpers.
  2. Read _extract_mention_ids() and _message_mentions_bot().
  3. Read the p2p branch in _admit() for FEISHU_DM_POLICY=disabled.
  4. Skim the two focused test sections above to see each behavior pinned directly.

@ohmyskyhigh
ohmyskyhigh force-pushed the fix/feishu-agent-to-agent-group-chat branch from b99c6c1 to 65e9c5a Compare July 14, 2026 18:12
@ohmyskyhigh

Copy link
Copy Markdown
Author

Rebased and force-pushed this branch onto current origin/main again.

Current head: 65e9c5a831fe60fe6fad6916b205e11a5b0b9a37

GitHub now reports:

mergeable: MERGEABLE
mergeStateStatus: BLOCKED

BLOCKED appears to be policy/review-state related rather than a merge conflict; the branch itself is no longer conflicted.

I rechecked the admin/sweeper requirements after the rebase:

  • implementation is on the current live adapter path: plugins/platforms/feishu/adapter.py;
  • added/kept the left-boundary regression for user@MOSS.com so outbound alias matching does not create native at tags inside email/fragments;
  • FEISHU_DM_POLICY=disabled is layered ahead of p2p only, preserving the existing p2p allow-all / pairing / allowlist behavior when open;
  • group admission remains independent of DM disabling;
  • FEISHU_MENTION_ALIASES / FEISHU_OUTBOUND_MENTION_ALIASES compatibility is preserved;
  • bare ou_... / u_... mention IDs normalize through the same extractor used by _message_mentions_bot();
  • the test files remain included because they are direct regression coverage for the runtime paths above.

Validation on the rebased branch:

git diff --check
# passed

python -m compileall -q plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# passed

ruff check plugins/platforms/feishu/adapter.py tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/feishu_helpers.py
# All checks passed!

python -m pytest tests/gateway/test_feishu.py::TestFeishuOutboundMentions tests/gateway/test_feishu_bot_admission.py -q -o 'addopts='
# 82 passed, 5 warnings in 1.34s

python -m pytest tests/gateway/test_feishu.py tests/gateway/test_feishu_bot_admission.py tests/gateway/test_feishu_bot_auth_bypass.py -q -o 'addopts='
# 298 passed, 5 warnings in 3.18s

python -m pytest tests/tools/test_send_message_tool.py -q -o 'addopts='
# 152 passed, 1 warning in 1.71s

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants